#include "ocr.h"
#include "ip.h"
#include <opencv/highgui.h>
#include <QFile>
#include <QTextStream>
#include <QStringList>
#include <QDir>
#include <limits>

#define DOUBLE_INFINITY std::numeric_limits<double>::infinity()

OCR::OCR()
{
}

QString OCR::recognize(const cv::Mat &m1)
{
  double min_dist = DOUBLE_INFINITY;
  cv::Mat m = normalize(m1);
  double n = m.rows*m.cols;
  QString ret;
  ReducedFeature f(m);
  const QVector<int>& candidates = m_map[f];
  Q_FOREACH (int c, candidates)
  {
    const TrainingPair& p = m_ts[c];
    const cv::Mat& x = p.x;
    QString y = p.y;
    if (x.rows == m.rows && x.cols == m.cols)
    {
      cv::Mat diff = x - m;
      double dist = cv::norm(diff) / n;
      if (min_dist > dist)
      {
        ret = y;
        min_dist = dist;
      }
    }
  }
  return ret;
}

void OCR::save(const cv::Mat &x, const QString &y)
{
  QString fn = getNextFileName();
  QStringList s = fn.split("/", QString::SkipEmptyParts);
  if (s.size() != 3) return;
  saveNextFileName(fn);
  QDir dir;
  if (!dir.exists(s[0]+"/"+s[1]+"/"))
    dir.mkpath(s[0]+"/"+s[1]+"/");
  cv::Mat norm = normalize(x);
  cv::imwrite((fn+".tif").toStdString(), norm);
  addTrainingPair(norm, y);
  QFile f("pairs");
  if (!f.open(QIODevice::Append | QIODevice::Text))
  {
    return;
  }
  QTextStream fs(&f);
  fs << fn+".tif" << " " << y << "\n";
}

QString OCR::getNextFileName()
{
  QFile f("nextfile");
  if (!f.open(QIODevice::Text | QIODevice::ReadOnly))
  {
    return "00/00/00";
  }
  QTextStream fs(&f);
  return fs.readLine();
}

void OCR::saveNextFileName(QString fn)
{
  QStringList s = fn.split("/", QString::SkipEmptyParts);
  if (s.size() != 3) return;
  int s0 = s[0].toUInt(), s1 = s[1].toUInt(), s2 = s[2].toUInt();
  s2++;
  if (s2 == 100)
  {
    s2 = 0;
    s1++;
    if (s1 == 100)
    {
      s1 = 0;
      s0++;
      if (s0 == 100) return;
    }
  }
  fn.sprintf("%02d/%02d/%02d", s0, s1, s2);
  QFile f("nextfile");
  if (!f.open(QIODevice::Text | QIODevice::WriteOnly))
    return;
  QTextStream fs(&f);
  fs << fn << "\n";
}

cv::Mat OCR::normalize(const cv::Mat& x)
{
  int r = 30, c = 30;
  cv::Mat xx = x.clone();
//  RGB bg(250, 250, 250);
//  for (int i = 0; i < xx.rows; i++)
//  {
//    for (int j = 0; j < xx.cols; j++) if (IP::getRGB(x, j, i) >= bg)
//    {
//      IP::getRGB(xx, j, i) = RGB(255, 255, 255);
//    }
//  }
  cv::Rect bb = boundingBox(xx);
  cv::Mat xroi = xx(bb);
  if (bb.height > r) r = (bb.height / r + (bb.height % r == 0 ? 0 : 1)) * r;
  if (bb.width > c) c = (bb.width / c + (bb.width % c == 0 ? 0 : 1)) * c;
  if (xroi.rows > r || xroi.cols > c)
  {
    QTextStream(stdout) << "Out of Bounds: rows = " << xroi.rows << " cols = " << xroi.cols << "\n";
    return cv::Mat();
  }
  int pr = r/2 - xroi.rows/2;
  int pc = c/2 - xroi.cols/2;
  cv::Mat ret(r, c, xroi.type(), cv::Scalar(255, 255, 255) );
  cv::Mat roi = ret(cv::Rect(pc, pr, xroi.cols, xroi.rows));
  xroi.copyTo(roi);
  //cv::rectangle(ret, cv::Rect(pc-1,pr-1,xroi.cols+2, xroi.rows+2), cv::Scalar(0,0,255));
  return ret;
}

cv::Rect OCR::boundingBox(const cv::Mat &m)
{
  RGB bg(255, 255, 255);
  int x=0, y=0, w=0, h=0;
//  for (int i = 0; i < m.rows; i++)
//  {
//    for (int j = 0; j < m.cols; j++)
//    {
//      QTextStream(stdout) << (IP::getRGB(m, i, j) != bg ? 1 : 0);
//    }
//    QTextStream(stdout) << "\n";
//  }
  for (int i = 0; i < m.rows; i++)
  {
    bool empty = true;
    for (int j = 0; j < m.cols; j++)
      if (IP::getRGB(m, i, j) != bg) { /*QTextStream(stdout) << "r1 " << i << "rgb = " << IP::getRGB(m, i, j).r << "\n";*/ empty = false; break; }
    if (!empty)
    {
      y = i;
      break;
    }
  }
  for (int i = m.rows-1; i >= 0; i--)
  {
    bool empty = true;
    for (int j = 0; j < m.cols; j++)
      if (IP::getRGB(m, i, j) != bg) { /*QTextStream(stdout) << "r2 " << i << IP::getRGB(m, i, j).r << "\n";*/ empty = false; break; }
    if (!empty)
    {
      h = i-y+1;
      break;
    }
  }
  for (int j = 0; j < m.cols; j++)
  {
    bool empty = true;
    for (int i = 0; i < m.rows; i++)
      if (IP::getRGB(m, i, j) != bg) { empty = false; break; }
    if (!empty)
    {
      x = j;
      break;
    }
  }
  for (int j = m.cols-1; j >= 0; j--)
  {
    bool empty = true;
    for (int i = 0; i < m.rows; i++)
      if (IP::getRGB(m, i, j) != bg) { empty = false; break; }
    if (!empty)
    {
      w = j-x+1;
      break;
    }
  }
//  QTextStream(stdout) << "x = " << m.cols << " " << m.rows
//      << " bb = " << x << " " << y << " " << w << " " << h << "\n";
  return cv::Rect(x,y,w,h);
}

void OCR::loadCurrentSet()
{
  m_ts.clear();
  QFile f("pairs");
  if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
  {
    return;
  }
  QTextStream fs(&f);
  while (!fs.atEnd())
  {
    QString line = fs.readLine();
    QStringList s = line.split(" ", QString::SkipEmptyParts);
    if (s.size() != 2) continue;
    QString fn = s[0];
    QString y = s[1];
    cv::Mat x = cv::imread(fn.toStdString());
    if (!x.empty())
    {
      QTextStream(stdout) << "x = " << fn << " " << "y = " << y << "\n";
      addTrainingPair(x, y);
    }
  }
}

void OCR::addTrainingPair(const cv::Mat& x, const QString& y)
{
  m_ts << TrainingPair(x, y);
  ReducedFeature f(x);
  m_map[f] << m_ts.size()-1;
}
